package org.threads.circlebuffer;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * My implementation of thread-safe blocking circle buffer,
 * based on Producer-Consumer pattern.
 */
public class CircleBuffer {

    public static final String CIRCLE_BUFFER_NULL_EX = "CircleBuffer is null!";
    private static final String BUFFER_SIZE_EX = "Buffer size can't be less then 1!";

    private final int size;
    private final Object[] buffer;

    private int start = 0;
    private int end = 0;
    private int count = 0;

    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    /**
     * Creates CircleBuffer object with specified buffer size.
     *
     * @param bufferSize size of circle buffer
     */
    public CircleBuffer(int bufferSize) {

        if (bufferSize < 1) {
            throw new IllegalArgumentException(BUFFER_SIZE_EX);
        }

        size = bufferSize;
        buffer = new Object[bufferSize];
    }

    /**
     * Obtains an object from buffer.
     *
     * @return object from buffer
     * @throws InterruptedException
     */
    public Object get() throws InterruptedException {

        lock.lock();

        Object get = null;

        try {
            while (count == 0) {
                notEmpty.await();
            }

            get = buffer[start++];
            count--;
            if (start == size) {
                start = 0;
            }

            notFull.signal();

        } finally {
            lock.unlock();
        }

        return get;
    }

    /**
     * Adds an object for storing in circle buffer.
     *
     * @param obj object to store
     * @throws InterruptedException
     */
    public void add(Object obj) throws InterruptedException {

        lock.lock();

        try {
            while (count == size) {
                notFull.await();
            }

            buffer[end++] = obj;
            count++;
            if (end == size) {
                end = 0;
            }

            notEmpty.signal();

        } finally {
            lock.unlock();
        }
    }

    /**
     * Gets the size of circle buffer.
     *
     * @return size
     */
    public int getSize() {
        return size;
    }


}
